package com.cm.res.scanner;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingDeque;

public class ResourceChecker implements Runnable {

    private static ResourceChecker instance;
    private final Queue<String> sPathQueue = new LinkedBlockingDeque<>(Integer.MAX_VALUE);
    private final Set<String> sResourceFiles = new ConcurrentSkipListSet<>();

    private volatile int taskSize = 0;
    private volatile int pathSize = 0;

    private volatile boolean hasFinish = false;
    private String[] resourceTypes;
    private String rootPath;

    public static ResourceChecker getInstance() {
        if (instance == null) {
            instance = new ResourceChecker();
        }
        return instance;

    }


    public void startCheck(String rootPath, String[] resourceTypes) {
        this.resourceTypes = resourceTypes;
        this.rootPath = rootPath;
        startScan(rootPath);
        new Thread(this, getClass().getName()).start();
    }

    public void findAllResources(String rootPath) {

        if (rootPath == null || rootPath.length() == 0) {
            return;
        }
        File rootFile = new File(rootPath);
        if (!rootFile.exists()) {
            return;
        }
        Stack<String> stack = new Stack<>();
        stack.push(rootPath);

        while (!stack.empty()) {

            String pop = stack.pop();
            if (pop == null || pop.length() == 0) continue;
            File popFile = new File(pop);

            if (popFile.isFile()) {

                String absolutePath = popFile.getAbsolutePath();
                notifyAddResource(rootFile, absolutePath);

            } else {
                File[] list = popFile.listFiles();
                if (list == null || list.length == 0) continue;
                for (int i = 0; i < list.length; i++) {
                    String absolutePath = list[i].getAbsolutePath();
                    if (canExclude(absolutePath)) continue;
                    stack.push(absolutePath);
                }
            }
        }

    }

    private void notifyAddResource(File rootFile, String absolutePath) {

        if (resourceTypes == null || resourceTypes.length == 0) return;

        for (int i = 0; i < resourceTypes.length; i++) {

            String[] subfixes = ResourceConstant.getSubfixes(resourceTypes[i]);
            if (subfixes == null || subfixes.length == 0) continue;
            String res = "res" + File.separator + resourceTypes[i];

            for (String subfix : subfixes) {

                if (!absolutePath.endsWith(subfix)) {
                    continue;
                }

                if (absolutePath.contains(res)) {
                    String resName = absolutePath.substring(rootFile.getAbsolutePath().length());
                    sResourceFiles.add(resName);
                }
            }

        }
    }

    public void notifyRemoveResource(String resPath) {
        sResourceFiles.remove(resPath);
        String tn = Thread.currentThread().getName();
        System.err.println("[" + tn + "]  delete: " + resPath);
    }



    public void startScan(String rootPath) {
        if (rootPath == null || rootPath.length() == 0) {
            return;
        }
        File f = new File(rootPath);
        if (!f.exists()) {
            return;
        }

        offerToQueue(f);
    }

    private void offerToQueue(File f) {
        synchronized (sPathQueue) {
            pathSize++;
            sPathQueue.offer(f.getAbsolutePath());
            sPathQueue.notifyAll();
        }
    }


    @Override
    public void run() {

        if (sPathQueue == null) return;
        findAllResources(rootPath);

        do {
            synchronized (sPathQueue) {
                if (sPathQueue.size() == 0) {
                    try {
                        sPathQueue.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    continue;
                }

                String path = sPathQueue.poll();
                taskSize++;
                ScannerTask scannerTask = getScannerTask(path, taskSize);
                ScannerTaskManager.getInstance().commitTask(scannerTask);

            }

        } while (!hasFinish);

    }


    private ScannerTask getScannerTask(String path, int id) {
        return new ScannerTask(path, id);

    }


    public synchronized Set<String> getResourceFiles() {
        return sResourceFiles;
    }

    public void onScanFinish(String filePath, int id) {
        String tn = Thread.currentThread().getName();
        int size = sResourceFiles.size();

        System.out.println("[" + tn + "]  finish :" + id + " " + filePath);
        System.out.println("taskSize=" + taskSize + ",pathSize=" + pathSize + ",ResSize=" + size);

        if (size == 0) {
            hasFinish = true;
        } else {
            boolean isFinish = ScannerTaskManager.getInstance().isFinish();
            if (!isFinish) {
                return;
            }
            synchronized (sPathQueue) {
                if (taskSize == pathSize && sPathQueue.size()==0) {
                    hasFinish = true;
                }
                sPathQueue.notifyAll();
            }
        }
        if (hasFinish) {
            ScannerTaskManager.getInstance().stopTask();
            synchronized (this) {
                this.notifyAll();
            }
        }

    }

    public boolean isHasFinish() {
        return hasFinish;
    }

    public Set<String> waitOnUntilFinish() {

        while (true) {
            synchronized (this) {
                try {
                    if (!isHasFinish()) {
                        wait();
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        return  sResourceFiles;

    }

    public String[] getResourceTypes() {
        return resourceTypes;
    }


    public static boolean canExclude(String absolutePath) {
        if (absolutePath.contains("/.idea")) return true;
        if (absolutePath.contains("/.gradle")) return true;
        if (absolutePath.contains("/.setting")) return true;
        if (absolutePath.contains("/.cache")) return true;
        if (absolutePath.contains("/build")) return true;
        if (absolutePath.contains("/gradle")) return true;
        if (absolutePath.endsWith(".bat")) return true;
        if (absolutePath.endsWith(".sh")) return true;
        if (absolutePath.endsWith(".iml")) return true;
        if (absolutePath.endsWith(".jar")) return true;
        if (absolutePath.endsWith("/.DS_Store")) return true;
        return false;
    }

    public void saveToResultFile(Set<String> resources, String rootPath) {

        if(resources==null) return;
        File file = new File(rootPath,"ResCheckerResult.txt");
        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream(file);
            for (String resPath : resources){
                if(resPath==null || resPath.length()==0) continue;
                fos.write(String.format("%s\r\n",resPath).getBytes());
                fos.flush();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            System.err.println("Scan Result has saved to ResCheckerResult.txt");
        }


    }

    public void deleteResultFile(String rootPath) {
        File file = new File(rootPath,"ResCheckerResult.txt");
        if(file.exists() && file.isFile()){
            file.delete();
        }
    }
}
